// fputest.cpp : Defines the entry point for the application.
//

#include "fputest.h"
#include <process.h>
#include <stdio.h>
#include <emmintrin.h>
#include <math.h>
#include <algorithm>

#define WIN32_LEAN_AND_MEAN             // Exclude rarely-used stuff from Windows headers
#define NOMINMAX
#include <windows.h>

#define TL_CPP 0
#include "tl/platform.h"

extern "C"
{
#include "tl/ieee.h"
#include "tl/cstdint.h"
#include "tl/approxmath/am.h"
}

#include <MMSystem.h>

#pragma comment(lib, "winmm.lib")

#define MAX_LOADSTRING 100

// Global Variables:
HINSTANCE hInst;								// current instance
TCHAR szTitle[MAX_LOADSTRING];					// The title bar text
TCHAR szWindowClass[MAX_LOADSTRING];			// the main window class name

BOOL				InitInstance(HINSTANCE, int);
INT_PTR CALLBACK	About(HWND, UINT, WPARAM, LPARAM);

int APIENTRY WinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPTSTR    lpCmdLine,
                     int       nCmdShow)
{
	UNREFERENCED_PARAMETER(hPrevInstance);
	UNREFERENCED_PARAMETER(lpCmdLine);

	// Initialize global strings
	//LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
	//LoadString(hInstance, IDC_FPUTEST, szWindowClass, MAX_LOADSTRING);
	//MyRegisterClass(hInstance);

	gvl_init_ieee();

	// Perform application initialization:
	if (!InitInstance (hInstance, nCmdShow))
	{
		return FALSE;
	}

	return 0;
}

//
//   FUNCTION: InitInstance(HINSTANCE, int)
//
//   PURPOSE: Saves instance handle and creates main window
//
//   COMMENTS:
//
//        In this function, we save the instance handle in a global variable and
//        create and display the main program window.
//
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
   HWND hWnd;

   hInst = hInstance; // Store instance handle in our global variable

   DialogBox(hInst, MAKEINTRESOURCE(IDD_DIALOG1), NULL, About);
   
   return TRUE;
}

static bool started = false;

#define WM_DONE       (WM_USER + 1000)
#define WM_START      (WM_USER + 1001)
#define WM_BENCH_DONE (WM_USER + 1002)
#define WM_ACC_DONE   (WM_USER + 1003)
#define WM_ALL_DONE   (WM_USER + 1004)

enum
{
	T_RCPSS,
	T_FMOD,
	T_FLOOR,
	T_EXP_BENCH_CEPHES,
	T_EXP_BENCH_SSE,
	T_EXP_BENCH_SSE_ALT,
	T_EXP_BENCH_FDLIBM,
	T_EXP_BENCH_CRT,
	T_EXP_ACC_SSE,
	T_EXP_ACC_SSE_ALT,
	T_EXP_ACC_CEPHES,
	T_EXP_ACC_CRT,

	T_SIN_BENCH_SSE,
	T_SIN_BENCH_SSE_ALT,
	T_SIN_BENCH_FDLIBM,
	T_SIN_BENCH_CRT,
	T_SIN_ACC_SSE,
	T_SIN_ACC_SSE_ALT,
	T_SIN_ACC_CRT,

	T_TAN_BENCH_SSE,
	T_TAN_BENCH_SSE_ALT,
	T_TAN_BENCH_CRT,
	T_TAN_ACC_SSE,
	T_TAN_ACC_SSE_ALT,
	T_TAN_ACC_CRT,
};

char const* names[] =
{
	"RCPSS",
	"fmod",
	"floor",
	"exp (cephes)",
	"exp (sse)",
	"exp (sse, alt)",
	"exp (fdlibm)",
	"exp (crt)",	
	"exp precision (sse)",
	"exp precision (sse, alt)",
	"exp precision (cephes)",
	"exp precision (crt)",

	"sin (sse)",
	"sin (sse, alt)",
	"sin (fdlibm)",
	"sin (crt)",
	"sin precision (sse)",
	"sin precision (sse, alt)",
	"sin precision (crt)",

	"tan (sse)",
	"tan (sse, alt)",
	"tan (crt)",
	"tan precision (sse)",
	"tan precision (sse, alt)",
	"tan precision (crt)",
};

union float_int
{
	float f;
	unsigned i;
};

union double_int
{
	double f;
	uint64_t i;
};


inline bool equal(double_int x, double_int y)
{
	return x.i == y.i || ((x.f != x.f) && (y.f != y.f));
}

inline bool equal(float_int x, float_int y)
{
	return x.i == y.i || ((x.f != x.f) && (y.f != y.f));
}

inline double relerror(double expected, double actual)
{
	if(expected != 0.0)
		return abs((actual - expected) / expected);
	else
		return 0.0;
}

struct hash
{
	hash()
	: a(0x1)
	{
		
	}

	void put(unsigned x)
	{
		a ^= x;
		a = (a >> 17) | (a << 15);
		a += x;
		a = (a >> 31) | (a << 33);
		a *= 0x27d4eb2d;
	}

	void put(uint64_t x)
	{
		put(uint32_t((x >> 32) & 0xffffffff));
		put(uint32_t(x & 0xffffffff));
	}

	unsigned get()
	{
		return unsigned(a) ^ unsigned(a >> 32);
	}

	unsigned long long a;
};

struct timer
{
	timer(HWND wnd, int func)
	: wnd(wnd), func(func)
	{
		SendMessage((HWND)wnd, WM_START, func, 0);
		before = timeGetTime();
	}

	~timer()
	{
		DWORD after = timeGetTime();
		SendMessage(wnd, WM_BENCH_DONE, func, after - before);
	}

	DWORD before;
	HWND wnd;
	int func;
};

struct accuracy
{
	accuracy(HWND wnd, int func)
	: wnd(wnd), func(func)
	, error(0.0), avg_error(0.0)
	, abs_error(0.0), abs_avg_error(0.0)
	, samples(0.0)
	{
		SendMessage((HWND)wnd, WM_START, func, 0);
	}

	~accuracy()
	{
		avg_error /= samples;
		abs_avg_error /= samples;
		SendMessage(wnd, WM_ACC_DONE, func, (LPARAM)this);
	}

	void reg(double expected, double actual)
	{
		double cur_abs_error = abs(expected - actual);
		double cur_error = relerror(expected, actual);

		if(cur_error > error)
			error = cur_error;
		if(cur_abs_error > abs_error)
			abs_error = cur_abs_error;

		avg_error += cur_error;
		abs_avg_error += cur_abs_error;
		samples += 1.0;
	}

	HWND wnd;
	int func;
	double error;
	double avg_error;
	double abs_error;
	double abs_avg_error;
	double samples;
};


typedef unsigned int uint32_t;

struct random
{
	random()
	{
		x = 123456789;
		y = 362436069;
		z = 521288629;
		w = 88675123;
	}

	uint32_t get() {
		uint32_t t;
 
		t = x ^ (x << 11);
		x = y; y = z; z = w;
		return w = w ^ (w >> 19) ^ (t ^ (t >> 8));
	}

	uint64_t get64() {
		uint32_t a = get();
		uint32_t b = get();
		return (uint64_t(a) << 32) + b;
	}

	double uniform() {
		double_int r;
		r.i = (get64() & 0x000fffffffffffffull) | 0x3ff0000000000000ull;
		return r.f - 1.0;
	}

	float uniformf() {
		float_int r;
		r.i = (get() & 0x007fffffull) | 0x3f800000ull;
		return r.f - 1.f;
	}

	float rangef(float min, float max)
	{
		return uniformf() * (max - min) + min;
	}

	float getf() {
		float_int r;
		r.i = get();
		return r.f;
	}

	uint32_t x;
	uint32_t y;
	uint32_t z;
	uint32_t w;
};

struct error_desc
{
	error_desc(int func)
	: func(func)
	, paramn(1)
	, isHash(false)
	{
		params[1] = 0.0;
	}

	int func;
	double params[2];
	int paramn;
	double actual;
	double expected;

	bool isHash;
	uint32_t h;
};


float expf_max = 88.0f;
float expf_min = -103.0f; /* log(2^-149) */

#define BENCHFUNC1(c,f_) do { \
		random rnd; timer _(wnd, (c)); \
		for(int i = 0; i < count/4; ++i) { \
			float _x = rnd.rangef(bncmin, bncmax); \
			float _y = _x + 0.005f; \
			float _z = _x - 0.01f; \
			float _w = _x + 0.01f; \
			sum += f_(_x); \
			sum += f_(_y); \
			sum += f_(_z); \
			sum += f_(_w); \
		} \
	} while(0)

#define ACCFUNC1(c,f,r) do { \
		random rnd; accuracy _(wnd, (c)); \
		for(int i = 0; i < count; ++i) { \
			float x = rnd.rangef(accmin, accmax); \
			float a = f(x), e = r(x); \
			_.reg(e, a); \
		} \
	} while(0)

void test_exp(HWND wnd)
{
	float sum = 0.f;

	int count = 10000000;
	
	{
		float bncmin = expf_min;
		float bncmax = expf_max;
		BENCHFUNC1(T_EXP_BENCH_CEPHES, cephes_expf);
		BENCHFUNC1(T_EXP_BENCH_SSE, am_expf);
		BENCHFUNC1(T_EXP_BENCH_SSE_ALT, am_expf_2);
		BENCHFUNC1(T_EXP_BENCH_FDLIBM, fd_exp);
		BENCHFUNC1(T_EXP_BENCH_CRT, expf);
	}

	{
		float accmin = -40.f;
		float accmax = 40.f;
		ACCFUNC1(T_EXP_ACC_SSE, am_expf, fd_exp);
		ACCFUNC1(T_EXP_ACC_SSE_ALT, am_expf_2, fd_exp);
		ACCFUNC1(T_EXP_ACC_CEPHES, cephes_expf, fd_exp);
		ACCFUNC1(T_EXP_ACC_CRT, expf, fd_exp);
	}

	printf("%f", sum);
}

void test_sin(HWND wnd)
{
	float sum = 0.f;

	int count = 10000000;

	{
		float bncmin = -40.f;
		float bncmax = 40.f;
		BENCHFUNC1(T_SIN_BENCH_SSE, am_sinf);
		BENCHFUNC1(T_SIN_BENCH_SSE_ALT, am_sinf_2);
		BENCHFUNC1(T_SIN_BENCH_FDLIBM, fd_sin);
		BENCHFUNC1(T_SIN_BENCH_CRT, sinf);
	}

	{
		float accmin = -40.f;
		float accmax = 40.f;
		ACCFUNC1(T_SIN_ACC_SSE, am_sinf, fd_sin);
		ACCFUNC1(T_SIN_ACC_SSE_ALT, am_sinf_2, fd_sin);
		ACCFUNC1(T_SIN_ACC_CRT, sinf, fd_sin);
	}

	printf("%f", sum);
}

void test_tan(HWND wnd)
{
	float sum = 0.f;

	int count = 10000000;

	{
		float bncmin = -1.56f;
		float bncmax = 1.56f;
		BENCHFUNC1(T_TAN_BENCH_SSE, am_tanf);
		BENCHFUNC1(T_TAN_BENCH_SSE_ALT, am_tanf_2);
		BENCHFUNC1(T_TAN_BENCH_CRT, tanf);
	}

	{
		float accmin = -1.56f;
		float accmax = 1.56f;
		ACCFUNC1(T_TAN_ACC_SSE, am_tanf, fd_tan);
		ACCFUNC1(T_TAN_ACC_SSE_ALT, am_tanf_2, fd_tan);
		ACCFUNC1(T_TAN_ACC_CRT, tanf, fd_tan);
	}

	printf("%f", sum);
}

void tester(void* user)
{
	HWND wnd = (HWND)user;

	SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_ABOVE_NORMAL);

	test_exp(wnd);
	test_sin(wnd);
	test_tan(wnd);

	SendMessage(wnd, WM_ALL_DONE, 0, 0);

#if 0
	{
		hash h;
		float step = 0.0001f;
		float zero = 0.f;
		__m128 x = _mm_load_ss(&zero);
		__m128 s = _mm_load_ss(&step);

		SendMessage((HWND)hWnd, WM_START, T_RCPSS, 0);

		for(int i = 0; i < 1000000; ++i)
		{
			float_int r;
			_mm_store_ss(&r.f, _mm_rcp_ss(x));

			x = _mm_add_ss(x, s);

			h.put(r.i);
		}

		error_desc* desc = 0;

		if(h.get() != 0x2424cb2c)
		{
			desc = new error_desc(T_RCPSS);
			desc->isHash = true;
			desc->h = h.get();
		}

		SendMessage((HWND)hWnd, WM_DONE, T_RCPSS, (LPARAM)desc);
	}

	{
		double_int x;
		double_int y;

		error_desc* desc = 0;
		
		SendMessage((HWND)hWnd, WM_START, T_FMOD, 0);

		int res = 0;
		int i = 0;
		for(i = 0; i < 1000000; ++i)
		{
			x.i = xor128_64();
			y.i = xor128_64();

			double_int r, e;
			r.f = fmod(x.f, y.f);
			e.f = fd_fmod(x.f, y.f);

			if(!equal(r, e))
			{
				desc = new error_desc(T_FMOD);
				desc->params[0] = x.f;
				desc->params[1] = y.f;
				desc->paramn = 2;
				desc->actual = r.f;
				desc->expected = e.f;
				break;
			}
		}

		SendMessage((HWND)hWnd, WM_DONE, T_FMOD, (LPARAM)desc);
	}

	{
		double_int x;
		double_int y;

		error_desc* desc = 0;
		
		SendMessage((HWND)hWnd, WM_START, T_FLOOR, 0);

		int res = 0;
		int i = 0;
		for(i = 0; i < 3000000000; ++i)
		{
			x.i = xor128_64();

			double_int r, e;
			r.f = floor(x.f);
			e.f = fd_floor(x.f);

			if(!equal(r, e))
			{
				desc = new error_desc(T_FLOOR);
				desc->params[0] = x.f;
				desc->paramn = 1;
				desc->actual = r.f;
				desc->expected = e.f;
				break;
			}
		}

		SendMessage((HWND)hWnd, WM_DONE, T_FLOOR, (LPARAM)desc);
	}
#endif
}

static void appendText(HWND hWnd, char const* str)
{
	HWND box = GetDlgItem(hWnd, IDC_RESULT);
	WPARAM len = SendMessage(box, WM_GETTEXTLENGTH, 0, 0);
	SendMessage(box, EM_SETSEL, len, len);
	SendMessage(box, EM_REPLACESEL, 0, (LPARAM)str);
}

// Message handler for about box.
INT_PTR CALLBACK About(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	UNREFERENCED_PARAMETER(lParam);
	switch (message)
	{
	case WM_INITDIALOG:
		return (INT_PTR)TRUE;

	case WM_CLOSE:
		EndDialog(hWnd, LOWORD(wParam));
		return (INT_PTR)TRUE;

	case WM_START:
	{
		char buf[256];
		sprintf(buf, "%s... ", names[wParam]);
		appendText(hWnd, buf);
		break;
	}

	case WM_DONE:
	{
		error_desc* desc = (error_desc*)lParam;
		if(desc == 0)
		{
			appendText(hWnd, "ok\n");
		}
		else
		{
			char buf[1024];
			
			if(desc->isHash)
				sprintf(buf, "FAIL, %08x\n", desc->h);
			else
			{
				sprintf(buf, "FAIL, (%f, %f), expected %f, got %f\n", desc->params[0], desc->params[1], desc->expected, desc->actual);
			}
			appendText(hWnd, buf);
		}
		break;
	}

	case WM_BENCH_DONE:
	{
		char buf[1024];
		sprintf(buf, "%.2f s\r\n", lParam / 1000.0);
		appendText(hWnd, buf);
		break;
	}

	case WM_ACC_DONE:
	{
		char buf[1024];
		accuracy* acc = (accuracy*)lParam;
		sprintf(buf, "peak: %e, avg: %e, abs peak: %e, abs avg: %e\r\n",
			acc->error, acc->avg_error,
			acc->abs_error, acc->abs_avg_error);
		appendText(hWnd, buf);
		break;
	}

	case WM_ALL_DONE:
	{
		started = false;
		appendText(hWnd, "done.");
		break;
	}

	case WM_COMMAND:
		if (LOWORD(wParam) == IDOK)
		{
			if(!started)
			{
				started = true;
				//appendText(hWnd, "Processing...\n");
				char vendor[13] = {};
				uint32_t info, features, features2, features3;

				char brand[48] = {};

				__asm
				{
					mov eax, 0
					cpuid
					mov DWORD PTR [vendor], ebx
					mov DWORD PTR [vendor+4], edx
					mov DWORD PTR [vendor+8], ecx
					mov eax, 1
					cpuid
					mov info, eax
					mov features2, edx
					mov features2, ecx
					mov features3, ebx

					mov eax, 80000000h
					cpuid
					cmp eax, 80000004h
					jb no_brand
					
					mov eax, 80000002h
					cpuid
					mov dword ptr [brand], eax
					mov dword ptr [brand+4], ebx
					mov dword ptr [brand+8], ecx
					mov dword ptr [brand+12], edx

					mov eax, 80000003h
					cpuid
					mov dword ptr [brand+16], eax
					mov dword ptr [brand+20], ebx
					mov dword ptr [brand+24], ecx
					mov dword ptr [brand+28], edx

					mov eax, 80000004h
					cpuid
					mov dword ptr [brand+32], eax
					mov dword ptr [brand+36], ebx
					mov dword ptr [brand+40], ecx
					mov dword ptr [brand+44], edx
				no_brand:
				}

				SetWindowText(GetDlgItem(hWnd, IDC_RESULT), "");

				char buf[1024];
				sprintf(buf,
					"Vendor: %s\r\n"
					"Info:   %08x %08x %08x %08x\r\n"
					"Brand:  %s\r\n",
					vendor,
					info, features, features2, features3, 
					brand);

				appendText(hWnd, buf);

				_beginthread(tester, 0, hWnd);
			}
			return (INT_PTR)TRUE;
		}
		break;

	default:
		return DefWindowProc(hWnd, message, wParam, lParam);
	}
	
	return 0;
}
